/**
 * 本文件用于定义用户模块接口传递的参数验证中间件
 */
const { mkdirp } = require('mkdirp')
const fs = require('fs')
const path = require('path')
const { v4 } = require('uuid')
const moment = require('moment')
const Joi = require('joi')
const { Op } = require('sequelize')
const R = require('@/models/Response')
const { User } = require('@/models/model')
const {
  wpIncreaseHset,
  wpHget,
  wpGet,
  wpSet,
  wpTtl,
  wpDel
} = require('@/framework/redis/redisFn')
const {
  FIVE_MINUTES,
  TEN_MINUTES,
  HALF_HOUR,
  ONE_HOUR,
  ONE_DAY
} = require('@/constants')
const { businessTypes } = require('@/constants/index')
const {
  IS_UNSAFE_INTEGER,
  IS_INTEGER,
  IS_UNSAFE_INTEGER_2,
  MSG_CODE
} = require('@/constants/joiSchema')
const { ERR_PWD_CNT, FORBID_LOGIN, USER_TOKEN } = require('@/constants/user')
const { ERROR_USER_CODE, ERROR_SYSTEM_CODE } = require('@/constants/errorCode')
const { pwdArr, userReg } = require('@/constants/validateReg')
const { dumpCheck: globalDumpCheck } = require('@/utils/dumpCheck')
const { MD5 } = require('@/utils/md5')
const { verifyToken, verify } = require('@/utils/jwt')
const { userLoginToken } = require('@/config/config.default')
const { effect } = require('@/config/reactive')
const { compareRole } = require('@/utils/common')

// 用户验证模型
let userSchema = {
  page: IS_INTEGER.min(1).default(1),
  pageSize: IS_INTEGER.min(1).default(999),
  roleId: IS_UNSAFE_INTEGER,
  username: Joi.string().pattern(userReg),
  password: Joi.string().pattern(pwdArr[0]),
  oldPassword: Joi.string().pattern(pwdArr[0]),
  email: Joi.string().email(),
  nickname: Joi.string().min(2).max(50),
  phone: Joi.string().length(11),
  avator: Joi.string(),
  userIds: Joi.array().items(IS_UNSAFE_INTEGER),
  status: IS_INTEGER.valid(0, 1)
}

// 字段重复验证
const dumpCheck = {
  /**
   * 校验用户名是否重复
   * @param {*} ctx
   * @param {string} username
   * @returns {Promise<boolean>}
   */
  checkUsername: async (ctx, username) => {
    if (!username) {
      return true
    }

    const usernameValidate = await User.findOne({
      where: {
        username
      },
      attributes: ['id']
    })
    if (usernameValidate) {
      ctx.body = R.error(ERROR_USER_CODE.USERNAME_DUMP_ERROR)
      return false
    }

    return true
  },

  /**
   * 检验手机号是否被占用
   * @param {*} ctx
   * @param {string} phone
   * @returns {Promise<boolean>}
   */
  checkPhone: async (ctx, phone) => {
    if (!phone) {
      return true
    }

    const user = await User.findOne({
      where: {
        phone
      },
      attributes: ['id']
    })
    if (user) {
      ctx.body = R.error(ERROR_USER_CODE.PHONE_DUMP_ERROR)
      return false
    }

    return true
  },

  /**
   * 检验邮箱是否被占用
   * @param {*} ctx
   * @param {*} phone
   * @returns {Promise<boolean>}
   */
  checkEmail: async (ctx, email) => {
    if (!email) {
      return true
    }

    const user = await User.findOne({
      where: {
        email
      },
      attributes: ['id']
    })
    if (user) {
      ctx.body = R.error(ERROR_USER_CODE.EMAIL_DUMP_ERROR)
      return false
    }

    return true
  }
}

/**
 * 用户注册接口的验证
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.registerValidate = async (ctx, next) => {
  // 校验参数格式
  const schema = Joi.object({
    username: userSchema.username.required(),
    password: userSchema.password.required(),
    phone: userSchema.phone,
    email: userSchema.email.required(),
    code: MSG_CODE.required()
  }).validate(ctx.request.body)
  // 参数校验失败
  if (schema.error) {
    console.log(schema.error)
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { username, email, phone, code } = ctx.request.body
  // 检验用户名是否被占用
  const b1 = await dumpCheck.checkUsername(ctx, username)
  if (!b1) {
    return
  }
  // 校验邮箱是否已被他人绑定
  const b2 = await dumpCheck.checkEmail(ctx, email)
  if (!b2) {
    return
  }
  // 校验手机号是否被他人绑定
  const b3 = await dumpCheck.checkPhone(ctx, phone)
  if (!b3) {
    return
  }

  // 验证验证码是否正确
  const redisCode = await wpGet('register-code-' + email)
  if (!redisCode || '' + redisCode !== code) {
    ctx.body = R.error(ERROR_USER_CODE.CODE_MSG_ERROR)
    return
  }

  await next()

  // 判断是否注册成功，清除验证码
  if (+ctx.body.code === 0) {
    await wpDel('register-code-' + email)
  }
}

/**
 * 用户登录接口的验证
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.loginValidate = async (ctx, next) => {
  /**
   * 密码多次错误提示消息的处理
   * @param {*} cnt 错误次数
   * @param {*} before 是否被禁止登录了
   * @returns
   */
  const pwdErrorHandle = (cnt, before = false) => {
    cnt = Math.floor(cnt / 5)
    const errMessages = ['', '5分钟', '10分钟', '30分钟', '1小时', '1天']
    if (before) {
      if (cnt >= 1) {
        ctx.body = R.error({
          code: ERROR_USER_CODE.LOGIN_FORBID.code,
          message: `禁止登录${errMessages[cnt]}`
        })
      } else {
        ctx.body = R.error({
          code: ERROR_USER_CODE.LOGIN_FORBID.code,
          message: `禁止登录`
        })
      }
      return
    }
    ctx.body = R.error(ERROR_USER_CODE.USERNAME_PASSWORD_ERROR)
  }

  const flag = await wpGet(FORBID_LOGIN + ctx.request.body.username)
  if (flag && Number(flag) === 1) {
    const cnt = await wpHget(ERR_PWD_CNT, ctx.request.body.username)
    pwdErrorHandle(Number(cnt), true)
  }

  // 验证参数格式
  const schema = Joi.object({
    username: Joi.string().required(),
    password: Joi.string().required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  // 验证参数正确性
  const { username, password } = ctx.request.body
  // 查询数据库，看是否存在这个用户
  const user = await User.findOne({
    where: {
      [Op.or]: [{ email: username }, { username: username }]
    }
  })
  // 用户名不正确
  if (!user) {
    ctx.body = R.error(ERROR_USER_CODE.USERNAME_PASSWORD_ERROR)
    return
  }

  const res = await MD5(password, user.salt)
  console.log(res)

  const user2 = await User.findOne({
    where: {
      [Op.or]: [{ email: username }, { username: username }],
      password: res
    }
  })
  // 用户名密码不匹配
  if (!user2) {
    // 用户名密码多次错误的处理，失败5次，禁止登录5分钟，失败十次，禁止登录10分钟，15-30，20-1hour，25-明天

    let cnt = await wpIncreaseHset(ERR_PWD_CNT, username)
    const timeForbid = {
      5: FIVE_MINUTES,
      10: TEN_MINUTES,
      15: HALF_HOUR,
      20: ONE_HOUR,
      25: ONE_DAY
    }
    if ([5, 10, 15, 20, 25].includes(Number(cnt))) {
      await wpSet(FORBID_LOGIN + username, 1, timeForbid[cnt])
    }
    pwdErrorHandle(cnt)
    return
  }

  const userInfo = {
    id: user2.id,
    username: user2.username
  }

  ctx.user = userInfo
  await next()
}

/**
 * 用户信息的校验，校验username，email，nickname，phone的格式
 * 和正确性（是否重复）
 * 参数格式{
 *  username？
 *  email?
 *  nickname?
 *  phone?
 * }
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.checkUserInfoValidate = async (ctx, next) => {
  // 参数格式验证，都是可选的，有则验证
  const schema = Joi.object({
    ...userSchema
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { phone, email, username } = ctx.request.body
  // 用户名是否重复
  const b1 = await dumpCheck.checkUsername(ctx, username)
  if (!b1) {
    return
  }
  // 校验邮箱是否已被他人绑定
  const b2 = await dumpCheck.checkEmail(ctx, email)
  if (!b2) {
    return
  }
  // 校验手机号是否被他人绑定
  const b3 = await dumpCheck.checkPhone(ctx, phone)
  if (!b3) {
    return
  }
  await next()
}

/**
 * 检验用户旧密码和新密码
 * @param {*} ctx
 * @param {*} next
 */
module.exports.checkRePassword = async (ctx, next) => {
  const schema = Joi.object({
    oldPassword: userSchema.oldPassword.required(),
    password: userSchema.password.required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { oldPassword } = ctx.request.body
  // 原密码和用户对的上
  const user = await User.findByPk(BigInt(ctx.user.id))
  if ((await MD5(oldPassword, user.salt)) !== user.password) {
    ctx.body = R.error(ERROR_USER_CODE.USERNAME_PASSWORD_ERROR)
    return
  }

  await next()
}

/**
 * 用户密码找回接口参数验证
 * @param {*} ctx
 * @param {*} next
 */
module.exports.userFindPwdValidate = async (ctx, next) => {
  const schema = Joi.object({
    code: MSG_CODE.required(),
    email: userSchema.email.required(),
    password: userSchema.password.required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  // 到达这里，说明邮箱，验证码，密码格式没有问题
  const { code, email } = ctx.request.body
  // 验证code是否对的上
  const redisToken = await wpGet('findPwd-code-' + email)
  if (!redisToken || redisToken + '' !== code) {
    ctx.body = R.error(ERROR_USER_CODE.CODE_MSG_ERROR)
  }

  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 启用/禁用用户账号的参数验证
 * @param {*} ctx
 * @param {*} next
 */
module.exports.setUserStatusValidate = async (ctx, next) => {
  const schema = Joi.object({
    userIds: userSchema.userIds.required(),
    status: userSchema.status.required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { userIds } = ctx.request.body
  const curUser = await User.findByPk(BigInt(ctx.user.id))
  for (let i = 0; i < userIds.length; i++) {
    const user = await User.findByPk(BigInt(userIds[i]))
    const res = compareRole(curUser.roleId, user.roleId)
    if (!res) {
      ctx.body = R.error(ERROR_SYSTEM_CODE.PERMISSION_ERROR)
      return
    }
  }
  // 验证userIds的合法性
  const b = await globalDumpCheck.checkUserIds(userIds)
  if (!b) {
    ctx.body = R.error(ERROR_USER_CODE.USER_ID_ERROR)
    return
  }

  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 删除用户的参数验证
 * @param {*} ctx
 * @param {*} next
 */
module.exports.delUsersValidate = async (ctx, next) => {
  const schema = Joi.object({
    userIds: userSchema.userIds.required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { userIds } = ctx.request.body
  // 验证userIds的合法性
  const curUser = await User.findByPk(BigInt(ctx.user.id))
  for (let i = 0; i < userIds.length; i++) {
    const user = await User.findByPk(BigInt(userIds[i]))
    const res = compareRole(curUser.roleId, user.roleId)
    if (!res) {
      ctx.body = R.error(ERROR_SYSTEM_CODE.PERMISSION_ERROR)
      return
    }
  }
  const b = await globalDumpCheck.checkUserIds(userIds)
  if (!b) {
    ctx.body = R.error(ERROR_USER_CODE.USER_ID_ERROR)
    return
  }

  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 列表查询用户
 * @param {*} ctx
 * @param {*} next
 */
module.exports.listUsersValidate = async (ctx, next) => {
  const schema = Joi.object({
    page: userSchema.page,
    pageSize: userSchema.pageSize,
    username: Joi.string().allow('')
  }).validate(ctx.query)

  if (schema.error) {
    console.log(schema.error)
    ctx.body = R.error(ERROR_SYSTEM_CODE.PARAMS_SCHEMA_ERROR)
    return
  }
  ctx.query = schema.value
  // console.log(ctx.query, schema.value)
  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 修改用户昵称接口参数验证
 * @param {*} ctx
 */
module.exports.updateUserNicknameValidate = async (ctx, next) => {
  // 校验参数格式
  const schema = Joi.object({
    nickname: userSchema.nickname.required()
  }).validate(ctx.request.body)
  // 参数校验失败
  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }
  const { nickname } = ctx.request.body
  // nickname不能和自己的一样
  const user = await User.findByPk(BigInt(ctx.user.id))

  if (user.nickname === nickname) {
    ctx.body = R.error(ERROR_USER_CODE.NICKNAME_DUMP_ERROR)
    return
  }

  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 修改用户邮箱接口参数验证
 * @param {*} ctx
 */
module.exports.updateUserEmailValidate = async (ctx, next) => {
  // 校验参数格式
  const schema = Joi.object({
    code: MSG_CODE.required(),
    email: userSchema.email.required()
  }).validate(ctx.request.body)
  // 参数校验失败
  if (schema.error) {
    console.log(schema.error)
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  const { email, code } = ctx.request.body

  const redisCode = await wpGet('bindEmail-code-' + email)

  if (!redisCode || redisCode + '' !== code) {
    ctx.body = R.error(ERROR_USER_CODE.CODE_MSG_ERROR)
    return
  }

  // 验证email和code的格式
  // 验证email和code是否重复

  const one = await User.findOne({
    where: {
      email
    }
  })

  if (one) {
    ctx.body = R.error(ERROR_USER_CODE.EMAIL_DUMP_ERROR)
    return
  }

  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 修改用户头像接口
 * @param {*} ctx
 */
module.exports.updateAvatorValidate = async (ctx, next) => {
  // console.log('传输的文件：',ctx.request.body)
  let req = ctx.request.body
  let base64 = req.avator.replace(/^data:image\/\w+;base64,/, '')
  let buffer = Buffer.from(base64, 'base64')

  const ext = req.avatorName.split('.').pop() // 获取上传文件扩展名

  // 对原始文件名进行修改，生成hash和时间

  // 创建文件夹
  const uploadPath = './src/public/upload/userAvator' // 这是我测试的路径
  const flag = fs.existsSync(uploadPath) // 判断文件夹是否存在
  // 同步创建多级文件夹
  if (!flag) {
    mkdirp.sync(uploadPath)
  } else {
    console.log('文件夹已经建立')
  }

  const newName =
    moment().format('YYYY-MM-DD') + '-' + v4() + '-' + req.avatorName
  // 文件全路径
  const filePath = `${uploadPath}/${newName}`
  console.log('上传成功后的文件路径', `${filePath}`)
  const url = await new Promise((resolve, reject) => {
    fs.writeFileSync(`${filePath}`, buffer)
    // resolve(buffer)
    resolve('/upload/userAvator/' + newName)
    // ctx.body = R.success({
    //   url:
    // })
  })
  ctx.request.body.avator = url
  await next()
}

/**
 * 更新用户个人信息接口的参数验证
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.updateUserInfoValidate = async (ctx, next) => {
  // 参数格式验证
  const schema = Joi.object({
    type: Joi.string()
      .valid('nickname', 'password', 'email', 'avator')
      .required(),
    nickname: userSchema.nickname,
    password: userSchema.password,
    email: userSchema.email,
    oldPassword: userSchema.oldPassword,
    code: MSG_CODE,
    avator: Joi.any(),
    avatorName: Joi.string()
  }).validate(ctx.request.body)

  if (schema.error) {
    console.log(schema.error)
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  // 根据传入需要修改的type类型，进行不同的参数验证
  const {
    type,
    nickname,
    email,
    code,
    password,
    oldPassword,
    avator,
    avatorName
  } = ctx.request.body

  if (type === 'nickname') {
    ctx.request.body = {
      nickname: nickname
    }
    await this.updateUserNicknameValidate(ctx, next)
  } else if (type === 'email') {
    ctx.request.body = {
      email,
      code
    }
    await this.updateUserEmailValidate(ctx, next)
  } else if (type === 'password') {
    ctx.request.body = {
      oldPassword,
      password
    }
    await this.checkRePassword(ctx, next)
  } else if (type === 'avator') {
    ctx.request.body = {
      avator,
      avatorName
    }
    await this.updateAvatorValidate(ctx, next)
  }
}

/**
 * 发送验证码接口的参数验证
 * @param {*} ctx
 * @param {*} next
 * @returns
 */
module.exports.sendEmailCodeValidate = async (ctx, next) => {
  const businessTypeList = businessTypes.map((item) => item.type)
  // 参数格式验证
  const schema = Joi.object({
    email: userSchema.email.required(),
    businessType: Joi.string()
      .valid(...businessTypeList)
      .required(),
    methodType: Joi.string().valid('phone', 'email').required()
  }).validate(ctx.request.body)

  if (schema.error) {
    ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
    return
  }

  // 根据类型的不同，看是否需要验证token权限
  const { businessType, methodType, email } = ctx.request.body

  const businessInfo = businessTypes.find((item) => item.type === businessType)

  // 验证token
  if (businessInfo.needToken) {
    var token = ctx.headers.authorization
    token = token ? token.split('Bearer ')[1] : null
    if (!token) {
      ctx.body = R.error(ERROR_USER_CODE.USER_TOKEN_ERROR)
      return
    }

    try {
      const secret = userLoginToken.secret

      const userInfo = await verify(token, secret)
      // console.log(userInfo)
      // 验证token的正确性，即token的确是该登录用户的
      let redisToken = await wpHget(USER_TOKEN, userInfo.id)
      redisToken = redisToken.token
      // console.log(redisToken, token)
      if (redisToken !== token) {
        ctx.body = R.error(ERROR_USER_CODE.USER_TOKEN_ERROR)
        return
      }
      // console.log(userInfo)
      ctx.user = {
        id: userInfo.id,
        username: userInfo.username
      }
    } catch {
      ctx.body = R.error(ERROR_USER_CODE.USER_TOKEN_ERROR)
      return
    }
    // token验证通过
  } else {
    // 不需要token时，ctx.user.id为email
    // ctx.user = {
    //   id: email
    // }
  }

  // 需要邮箱或者手机号存在
  if (businessInfo.needExist) {
    if (methodType === 'email') {
      const isNotDump = await dumpCheck.checkEmail({}, email)
      if (isNotDump) {
        // 邮箱不存在
        ctx.body = R.error(ERROR_USER_CODE.EMAIL_NOT_EXIST_ERROR)
        return
      }
    }
  }

  // 需要确保同一邮箱或者手机号在1分种内不能再次发送
  const resValue = await wpTtl(businessInfo.type + '-code-' + email)
  if (resValue && resValue > 240) {
    ctx.body = R.error(ERROR_USER_CODE.CODE_SEND_LIMIT_ERROR)
    return
  }
  await next()
}

/**
 * @author: Hejh(3010733382@qq.com)
 * @description: 用户校验滑块验证码的中间件，这个应该侵入到业务里面
 * @param {*} ctx
 */
module.exports.validateSlideCode = (needValide = true) => {
  return async (ctx, next) => {
    if (needValide) {
      const obj = {
        picCode: ctx.request.body.picCode
      }
      const schema = Joi.object({
        picCode: Joi.string().required()
      }).validate(obj)
      // 参数校验失败
      if (schema.error) {
        ctx.body = R.error(ERROR_USER_CODE.PARAMS_SCHEMA_ERROR)
        return
      }
      const { ip, ua } = ctx.mySession
      const { picCode } = ctx.request.body
      // redis存储验证码
      const res = await wpGet('slide-code-' + ip + ua)
      if (!res || res + '' !== picCode) {
        ctx.body = R.error(ERROR_USER_CODE.CODE_MSG_ERROR)
        return
      }

      // 到这里，验证成功，删除一下redis
      await wpDel('slide-code-' + ip + ua)
    }
    delete ctx.request.body.picCode
    await next()
  }
}
